﻿using System;
using System.Data;
using System.Data.Entity;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web;
using System.Web.UI.WebControls;
using Antlr.Runtime;
using DiarySocial.DAL;
using DiarySocial.Models;
using DiarySocial.ViewModels;
using DiarySocial.ViewModels.Profile;
using DotNetOpenAuth.Messaging;
using WebGrease.Css.Extensions;
using WebMatrix.WebData;

namespace DiarySocial.BLL
{
    public class DiaryBLL : IDisposable
    {
        private const int DEFAULT_AVATAR_ID = 1;
        private UnitOfWork unitOfWork = new UnitOfWork();

        #region Dispose

        private bool _disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                unitOfWork.Dispose();
            }
            this._disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region Business Logic

        public ICollection<TimeLineItemVM> GetMyStories(int userId)
        {
            return this.GetMyStories(userId, AppConst.DEFAULT_OFFSET, AppConst.DEFAULT_TAKE);
        }

        public ICollection<TimeLineItemVM> GetMyStories(int userId, int offset, int take)
        {
            var diaries = unitOfWork.DiaryRepository.dbSet
                .Include(d => d.Author)
                .Include(d => d.LikedUsers)
                .Include(d => d.Comments)
                .Include(d => d.Comments.Select(c => c.Author))
                .Where(d => d.AuthorId == userId)
                .OrderByDescending(d => d.Created)
                .Skip(offset)
                .Take(take)
                .ToList();
            return DiariesToStories(userId, diaries);
        }

        private static ICollection<TimeLineItemVM> DiariesToStories(int userId, IEnumerable<Diary> diaries)
        {
            var list = new List<TimeLineItemVM>();
            foreach (var diary in diaries)
            {
                var item = new TimeLineItemVM()
                {
                    Id = diary.DiaryId,
                    Body = diary.Body,
                    Time = diary.Created,
                    LikeCount = diary.LikedUsers.Count,
                    CommentCount = diary.Comments.Count,
                    Author = new AuthorVM(diary.Author.UserName),
                    Privacy = (int)diary.Privacy,
                };
                foreach (var comment in diary.Comments)
                {
                    item.Comments.Add(new CommentVM()
                    {
                        Id = comment.CommentId,
                        Text = comment.Text,
                        Time = comment.Created,
                        CommentAuthor = new AuthorVM(comment.Author.UserName)
                    });
                }
                if (diary.LikedUsers.FirstOrDefault(l => l.UserId == userId) != null)
                {
                    item.IsLiked = true;
                }
                list.Add(item);
            }
            return list;
        }

        public ICollection<TimeLineItemVM> GetFriendStories(int userId)
        {
            return GetFriendStories(userId, AppConst.DEFAULT_OFFSET, AppConst.DEFAULT_TAKE);
        }

        public ICollection<TimeLineItemVM> GetFriendStories(int userId, int offset, int take)
        {
            var diaryIds = unitOfWork.DiaryRepository.GetDiaryIdsOfFriends(userId, offset, take);
            var q = unitOfWork.DiaryRepository.dbSet
                .Include(d => d.Author)
                .Include(d => d.LikedUsers)
                .Include(d => d.Comments)
                .Include(d => d.Comments.Select(c => c.Author))
                .Where(d => diaryIds.Contains(d.DiaryId))
                .OrderByDescending(d => d.Created);
            var diaries = q.ToList();
            return DiariesToStories(userId, diaries);
        }

        public ICollection<TimeLineItemVM> GetFriendStoriesNewerThanId(int userId, int lastId)
        {
            var diaryIds = unitOfWork.DiaryRepository.GetNewDiaryIdsOfFriends(userId, lastId, AppConst.DEFAULT_MAX_TAKE);
            var q = unitOfWork.DiaryRepository.Get(
                filter: d => diaryIds.Contains(d.DiaryId),
                orderBy: d => d.OrderByDescending(m => m.Created),
                includeProperties: "Author, LikedUsers, Comments, Comments.Author");
            var diaries = q.ToList();
            return DiariesToStories(userId, diaries);
        }

        public object JLike(int userId, int diaryId, bool isLike)
        {
            var diary =
                unitOfWork.DiaryRepository.Get(filter: d => d.DiaryId == diaryId, includeProperties: "LikedUsers")
                    .SingleOrDefault();
            var success = false;
            var likeCount = 0;
            if (diary != null)
            {
                likeCount = diary.LikedUsers.Count;
                var liked = diary.LikedUsers.FirstOrDefault(l => l.UserId == userId);
                if (liked != null && !isLike)
                {
                    diary.LikedUsers.Remove(liked);
                    unitOfWork.DiaryRepository.Update(diary);
                    unitOfWork.SaveChanges();
                    likeCount--;
                }
                else if (isLike)
                {
                    var user = unitOfWork.UserRepository.GetByID(userId);
                    diary.LikedUsers.Add(user);
                    unitOfWork.DiaryRepository.Update(diary);
                    unitOfWork.SaveChanges();
                    likeCount++;
                }
                success = true;
            }
            var result = new
            {
                success = success,
                like = isLike,
                likeCount = likeCount
            };
            return result;
        }

        public object JLikeError()
        {
            var result = new
            {
                success = false,
                like = false,
                likeCount = 0
            };
            return result;
        }

        public object JGetLikedUsers(int userId, int diaryId)
        {
            var success = false;
            var likeCount = 0;
            var diary =
                unitOfWork.DiaryRepository.Get(filter: d => d.DiaryId == diaryId, includeProperties: "LikedUsers")
                    .SingleOrDefault();
            List<AuthorVM> likedUsers = null;
            if (diary != null)
            {
                likeCount = diary.LikedUsers.Count;
                likedUsers = diary.LikedUsers.Select(d => new AuthorVM()
                {
                    Avatar = AppConst.DEFAULT_AVATAR_URL,
                    AuthorLink = "#",
                    Name = d.UserName
                }).ToList();
                success = true;
            }
            var result = new
            {
                success = success,
                likeCount = likeCount,
                likedUsers = likedUsers
            };
            return result;
        }

        public bool QuickPost(int userId, string body, int privacy)
        {
            body = body.Trim();
            if (String.IsNullOrEmpty(body)) return false;
            var diary = new Diary(body, privacy, userId);
            unitOfWork.DiaryRepository.Insert(diary);
            unitOfWork.SaveChanges();
            return true;
        }

        public GetCommentsVM GetComments(int userId, int diaryId)
        {
            var result = new GetCommentsVM();
            if (!HasViewPermissionOnPost(userId, diaryId)) return result;
            result.DiaryId = diaryId;
            var comments = unitOfWork.CommentRepository.Get(filter: t => t.DiaryId == diaryId, includeProperties: "Author",
                orderBy: t => t.OrderBy(c => c.Created));
            result.Comments = VMFactory.commentModelToCommentVM(comments);
            return result;
        }



        private bool HasViewPermissionOnPost(int userId, int diaryId)
        {
            var diary = unitOfWork.DiaryRepository.GetByID(diaryId);
            if (diary == null) throw new DiaryBLLExeption("Diary Not Found", new HttpException(404, "Not Found"));
            return HasViewPermissionOnPost(userId, diary);
        }

        private bool HasViewPermissionOnPost(int userId, Diary diary)
        {
            if (userId == diary.AuthorId) return true;
            switch (diary.Privacy)
            {
                case (int)PrivacyType.Private:
                    return userId == diary.AuthorId;
                case (int)PrivacyType.Friend:
                    return IsFriend(userId, diary.AuthorId) || IsFriend(diary.AuthorId, userId);
                case (int)PrivacyType.Invited:
                    return IsInvited(diary.DiaryId, userId);
                case (int)PrivacyType.Public:
                case (int)PrivacyType.Anonymous:
                    return true;
                default:
                    return false;
            }
        }

        private bool IsFriend(int userId, int friendId)
        {
            var user = unitOfWork.UserRepository.Get(
                filter: u => u.UserId == userId,
                includeProperties: "Friend_Friends").Single();
            return user.Friend_Friends.FirstOrDefault(f => f.UserId == friendId) != null;
        }

        private bool IsInvited(int diaryId, int invitedUserId)
        {
            return
                unitOfWork.DiaryPermissionRepository.Get(
                    filter: p => p.DiaryId == diaryId && p.InvitedUserId == invitedUserId && p.IsAllowed).SingleOrDefault() != null;
        }

        #endregion

        public void SendComment(int userId, int diaryId, string commentText)
        {
            var diary = unitOfWork.DiaryRepository.GetByID(diaryId);
            if (diary == null) throw new DiaryBLLExeption("Diary Not Found", new HttpException(404, "Not Found"));
            if (diary.IsCommentClosed) throw new DiaryBLLExeption("Comment closed");
            if (!HasWriteCommentPermissionOnPost(userId, diary)) throw new DiaryBLLExeption("You havenot permission to write comment");
            var comment = ModelFactory.CreateComment(userId, diaryId, commentText);
            unitOfWork.CommentRepository.Insert(comment);
            unitOfWork.SaveChanges();
        }

        private bool HasWriteCommentPermissionOnPost(int userId, Diary diary)
        {
            return HasViewPermissionOnPost(userId, diary);
        }

        public bool updateAbout(int userId, About about)
        {
            return unitOfWork.DiaryRepository.UpdateAbout(userId, about.Email, about.DisplayName, about.Job,
                about.School, about.Address, about.Introduce);
        }

        public void RemoveAvatar(int userId)
        {
            var userInfo =
                unitOfWork.ExtraUserInformationRepository.Get(filter: e => e.UserId == userId).FirstOrDefault();
            if (userInfo == null) return;
            userInfo.AvatarId = DEFAULT_AVATAR_ID;
            unitOfWork.ExtraUserInformationRepository.Update(userInfo);
            unitOfWork.SaveChanges();
        }

        public About getAboutContent(int userId, string acount)
        {
            var userInfo =
                unitOfWork.ExtraUserInformationRepository.Get(filter: e => e.UserId == userId, includeProperties: "Avatar").FirstOrDefault();
            if (userInfo == null) return new About();
            var aboutContent = new About()
            {
                Email = userInfo.Email ?? "",
                DisplayName = userInfo.Name ?? "",
                Avatar = userInfo.Avatar.Path ?? AppConst.DEFAULT_AVATAR_URL,
                Job = userInfo.Job ?? "",
                School = userInfo.School ?? "",
                Address = userInfo.Address ?? "",
                Introduce = userInfo.Introduce ?? "",
                Account = acount,
            };
            if (String.IsNullOrWhiteSpace(aboutContent.DisplayName)) aboutContent.DisplayName = acount;
            return aboutContent;
        }

        public void ChangeAvatar(int userId, string avatar, string defaultDisplayName)
        {
            var userInfo = unitOfWork.ExtraUserInformationRepository.Get(filter: e => e.UserId == userId).FirstOrDefault();
            if (userInfo != null)
            {
                var media = new Medium() { Path = avatar };
                media.ExtraUserInformations.Add(userInfo);
                unitOfWork.MediumRepository.Insert(media);
                unitOfWork.SaveChanges();
            }
            else
            {
                userInfo = new ExtraUserInformation()
                {
                    UserId = userId,
                    Email = "",
                    Name = defaultDisplayName,
                    Avatar = new Medium()
                    {
                        Path = avatar,
                    },
                };
                unitOfWork.ExtraUserInformationRepository.Insert(userInfo);
                unitOfWork.SaveChanges();
            }
        }

        public Friends getFriendContent(int userId, int yourId)
        {
            var friends = new Friends();
            var user = unitOfWork.UserRepository.Get(filter: u => u.UserId == userId, includeProperties: "Friend_Friends, Friend_Friends.ExtraUserInformations, Friend_Friends.ExtraUserInformations.Avatar").Single();
            var list = (from friend in user.Friend_Friends
                        let extraInfo = friend.ExtraUserInformations.FirstOrDefault()
                        select new FriendItem()
                        {
                            Avatar = extraInfo == null ? AppConst.DEFAULT_AVATAR_URL : extraInfo.Avatar.Path,
                            DisplayName = extraInfo == null ? friend.UserName : extraInfo.Name,
                            Description = extraInfo == null ? "" : (String.IsNullOrWhiteSpace(extraInfo.Job) ? extraInfo.School ?? "" : extraInfo.Job),
                            ProfileUrl = "/Diary/Profile/" + friend.UserName,
                        }).ToList();
            friends.AllFriends.AddRange(list);
            var excludeIds = new HashSet<int>(from friendItem in user.Friend_Friends
                                              select friendItem.UserId);
            var sentRequests = unitOfWork.FriendRequestRepository.Get(filter: u => u.SenderId == yourId);
            excludeIds.AddRange(from friendItem in sentRequests select friendItem.ReceiverId);
            var reciveRequests = unitOfWork.FriendRequestRepository.Get(filter: u => u.ReceiverId == yourId);
            var friendRequests = reciveRequests as IList<FriendRequest> ?? reciveRequests.ToList();
            excludeIds.AddRange(from friendItem in friendRequests select friendItem.SenderId);
            excludeIds.Add(yourId);
            var suggests = unitOfWork.UserRepository.Get(filter: u => !excludeIds.Contains(u.UserId), includeProperties: "ExtraUserInformations, ExtraUserInformations.Avatar", orderBy: o => o.OrderBy(x => Guid.NewGuid())).Take(count: AppConst.DEFAULT_FRIEND_WIDGET);
            foreach (var friend in suggests)
            {
                var extraInfo = friend.ExtraUserInformations.FirstOrDefault();
                friends.SuggestedFriends.Add(new FriendItem()
                {
                    Avatar = extraInfo == null ? AppConst.DEFAULT_AVATAR_URL : extraInfo.Avatar.Path,
                    DisplayName = extraInfo == null ? friend.UserName : extraInfo.Name,
                    Description = extraInfo == null ? "" : (String.IsNullOrWhiteSpace(extraInfo.Job) ? extraInfo.School ?? "" : extraInfo.Job),
                    ProfileUrl = "/Diary/Profile/" + friend.UserName,
                });
            }

            if (userId == yourId)
            {
                list = (from request in friendRequests
                        let extraInfo = request.SenderUser.ExtraUserInformations.FirstOrDefault()
                        select new FriendItem()
                        {
                            Avatar = extraInfo == null ? AppConst.DEFAULT_AVATAR_URL : extraInfo.Avatar.Path,
                            DisplayName = extraInfo == null ? request.SenderUser.UserName : extraInfo.Name,
                            Description = extraInfo == null ? "" : (String.IsNullOrWhiteSpace(extraInfo.Job) ? extraInfo.School ?? "" : extraInfo.Job),
                            ProfileUrl = "/Diary/Profile/" + request.SenderUser.UserName,
                        }).ToList();
                friends.RequestedFriends.AddRange(list);
            }
            return friends;
        }

        public int getLastDiaryId(int userId)
        {
            var diary = unitOfWork.DiaryRepository.Get(filter: d => d.AuthorId == userId,
                orderBy: d => d.OrderByDescending(x => x.Created)).FirstOrDefault();
            return diary == null ? 0 : diary.DiaryId;
        }
    }
}